/*
 * Copyright (C) 2013  Forklabs Daniel Léonard
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package ca.forklabs.wow.net.ssl.certificates;

import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import ca.forklabs.baselib.util.CharsetPropertiesControl;

import java.security.cert.CertificateException;

/**
 * Class {@code ImportCertificatesFromBattlenetComCn} loads the certificate
 * chain for the Chinese battle net server.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.wow.net.ssl.certificates.ImportCertificatesFromBattlenetComCn">Daniel Léonard</a>
 * @version $Revision$
 */
public final class ImportCertificatesFromBattlenetComCn {

//---------------------------
// Class variables
//---------------------------

   /** Indicates if the certificates have been loaded. */
   private static boolean certificates_imported = false;


//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    */
   private ImportCertificatesFromBattlenetComCn() {
   // nothing
      }


//---------------------------
// Class methods
//---------------------------

   /**
    * Determines if the certificates have been loaded.
    * @return   {@code true} if the certificates have been loaded,
    *           {@code false} otherwise.
    */
   public static synchronized boolean areCertificatesImported() {
      return certificates_imported;
      }

   /**
    * Imports the certificates for the Chinese region battle.net server.
    */
   public static synchronized void importCertificates() {
      if (areCertificatesImported()) {
         return;
         }

      try {
      // 1) Load the certificate chain
         final Set<X509Certificate> certificates = new HashSet<X509Certificate>();

         InputStream is = ImportCertificatesFromBattlenetComCn.class.getResourceAsStream("battlenet.com.cn.p7b"); //$NON-NLS-1$
         try {
            CertificateFactory factory = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
            Collection<? extends Certificate> collection = factory.generateCertificates(is);
            for (Certificate certificate : collection) {
               X509Certificate x509 = (X509Certificate) certificate;
               certificates.add(x509);
               }
            }
         finally {
            if (null != is) {
               is.close();
               }
            }

      // 2) set up the default trust manager
         TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         factory.init((KeyStore) null);
         final X509TrustManager default_trust_manager = (X509TrustManager) factory.getTrustManagers()[0];

      // 3) set up my own manager
         TrustManager[] trust_managers = new TrustManager[] {
            new X509TrustManager() {
               public X509Certificate[] getAcceptedIssuers() {
                  throw new UnsupportedOperationException("getAcceptedIssuers");
                  }

               public void checkClientTrusted(X509Certificate[] chain,
                     String authType) throws CertificateException {
                  throw new UnsupportedOperationException("checkClientTrusted");
                  }

               public void checkServerTrusted(X509Certificate[] chain,
                     String authType) throws CertificateException {
                  // check if we trust
                  boolean is_trusted = true;
                  for (int i = 0, len = chain.length; is_trusted && i < len; i++) {
                     is_trusted &= certificates.contains(chain[i]);
                     }

                  // if we don't, check if it is trusted by the default
                  if (false == is_trusted) {
                     default_trust_manager.checkServerTrusted(chain, authType);
                     // if it is not trusted by the default, it will throw the
                     // exception for us
                     }
                  }
               },
            };

      // 4) use our trust manager
         SSLContext ssl_context = SSLContext.getInstance("SSL"); //$NON-NLS-1$
         ssl_context.init(null, trust_managers, null);
         HttpsURLConnection.setDefaultSSLSocketFactory(ssl_context.getSocketFactory());

         certificates_imported = true;
         }
      catch (Exception e) {
         Logger logger = Logger.getLogger(ImportCertificatesFromBattlenetComCn.class.getName());
         logger.log(Level.WARNING, getCouldNotInstallErrorMessage(), e);
         }
      }


//---------------------------
// i18n class methods
//---------------------------

   /** Key for the error message if the certificates could not be installed. */
   @SuppressWarnings("nls")
   private static final String COULD_NOT_INSTALL = "could.not.install";

   /**
    * Gets a generic resource not found JSON message.
    * @return   the error message in JSON format.
    */
   private static String getCouldNotInstallErrorMessage() {
      String message = getLocalizedString(COULD_NOT_INSTALL);
      return message;
      }

   /**
    * Gets and formats the specified localized string from the menu resource
    * bundle.
    * @param   key   the key.
    * @param   arguments   the arguments to format the string.
    * @return   the value.
    */
   private static String getLocalizedString(String key, Object... arguments) {
      String name = ImportCertificatesFromBattlenetComCn.class.getName();
      ResourceBundle bundle = ResourceBundle.getBundle(name, CharsetPropertiesControl.UTF_8_CONTROL);
      String pattern = bundle.getString(key);
      String message = MessageFormat.format(pattern, arguments);
      return message;
      }

   }
